use crate::rules::Result;
use pretty_assertions::assert_eq;

use super::*;

#[test]
fn yaml_loader() -> Result<()> {
    let docs = r###"
#    apiVersion: v1
#    next: true
#    number: 3
#    spec:
#      containers:
#        - image: blah
#          second: true
    Name: !Sub
      - www.${Domain}
      - { Domain: !Ref RootDomainName }
    "###;

    let mut loader = Loader::new();
    let value = loader.load(String::from(docs))?;

    let expected_string = r#"Map({("Name", Location { line: 8, col: 4 }): Map({("Fn::Sub", Location { line: 8, col: 10 }): List([String("www.${Domain}", Location { line: 9, col: 8 }), Map({("Domain", Location { line: 10, col: 10 }): Map({("Ref", Location { line: 10, col: 18 }): String("RootDomainName", Location { line: 10, col: 18 })}, Location { line: 10, col: 18 })}, Location { line: 10, col: 8 })], Location { line: 8, col: 10 })}, Location { line: 8, col: 10 })}, Location { line: 8, col: 4 })"#;
    let result_as_string = format!("{:?}", value);
    assert_eq!(expected_string, result_as_string);

    Ok(())
}

#[rstest::rstest]
#[case::standard_lowercase_true("true", true)]
#[case::standard_capitalized_true("True", true)]
#[case::standard_uppercase_true("TRUE", true)]
#[case::yaml_yes_lowercase("yes", true)]
#[case::yaml_yes_capitalized("Yes", true)]
#[case::yaml_yes_uppercase("YES", true)]
#[case::yaml_on_lowercase("on", true)]
#[case::yaml_on_capitalized("On", true)]
#[case::yaml_on_uppercase("ON", true)]
#[case::yaml_y_lowercase("y", true)]
#[case::yaml_y_uppercase("Y", true)]
#[case::standard_lowercase_false("false", false)]
#[case::standard_capitalized_false("False", false)]
#[case::standard_uppercase_false("FALSE", false)]
#[case::yaml_no_lowercase("no", false)]
#[case::yaml_no_capitalized("No", false)]
#[case::yaml_no_uppercase("NO", false)]
#[case::yaml_off_lowercase("off", false)]
#[case::yaml_off_capitalized("Off", false)]
#[case::yaml_off_uppercase("OFF", false)]
#[case::yaml_n_lowercase("n", false)]
#[case::yaml_n_uppercase("N", false)]
fn test_handle_bool_happy_path(#[case] arg: &str, #[case] expected: bool) -> Result<()> {
    let docs = format!("check: {arg}");

    let mut loader = Loader::new();
    match loader.load(String::from(docs))? {
        MarkedValue::Map(map, ..) => {
            assert!(map.len() == 1);
            let (.., result) = map.first().unwrap();

            if let MarkedValue::Bool(result, ..) = *result {
                assert_eq!(result, expected);
            }
        }
        _ => unreachable!("this isn't possible"),
    }

    Ok(())
}

#[test]
fn yaml_loader2() -> Result<()> {
    let docs = r###"
Resources:
  MyEc2Thing:
    Type: AWS::EC2::CapacityReservation
    Properties:
      AvailabilityZone: "some string"
      EbsOptimized: true
      EndDate: "12/31/2023"
      EphemeralStorage: false
      InstanceCount: 312
      InstanceMatchCriteria: "open"
      TagSpecifications:
      - ResourceType: instance
        Tags:
        - Key: Name
          Value: CFN EC2 Spot Instance
      Tenancy: String
    "###;

    let mut loader = Loader::new();
    let value = loader.load(String::from(docs))?;

    let expected_string = r#"Map({("Resources", Location { line: 1, col: 0 }): Map({("MyEc2Thing", Location { line: 2, col: 2 }): Map({("Type", Location { line: 3, col: 4 }): String("AWS::EC2::CapacityReservation", Location { line: 3, col: 10 }), ("Properties", Location { line: 4, col: 4 }): Map({("AvailabilityZone", Location { line: 5, col: 6 }): String("some string", Location { line: 5, col: 24 }), ("EbsOptimized", Location { line: 6, col: 6 }): Bool(true, Location { line: 6, col: 20 }), ("EndDate", Location { line: 7, col: 6 }): String("12/31/2023", Location { line: 7, col: 15 }), ("EphemeralStorage", Location { line: 8, col: 6 }): Bool(false, Location { line: 8, col: 24 }), ("InstanceCount", Location { line: 9, col: 6 }): Int(312, Location { line: 9, col: 21 }), ("InstanceMatchCriteria", Location { line: 10, col: 6 }): String("open", Location { line: 10, col: 29 }), ("TagSpecifications", Location { line: 11, col: 6 }): List([Map({("ResourceType", Location { line: 12, col: 8 }): String("instance", Location { line: 12, col: 22 }), ("Tags", Location { line: 13, col: 8 }): List([Map({("Key", Location { line: 14, col: 10 }): String("Name", Location { line: 14, col: 15 }), ("Value", Location { line: 15, col: 10 }): String("CFN EC2 Spot Instance", Location { line: 15, col: 17 })}, Location { line: 14, col: 10 })], Location { line: 14, col: 8 })}, Location { line: 12, col: 8 })], Location { line: 12, col: 6 }), ("Tenancy", Location { line: 16, col: 6 }): String("String", Location { line: 16, col: 15 })}, Location { line: 5, col: 6 })}, Location { line: 3, col: 4 })}, Location { line: 2, col: 2 })}, Location { line: 1, col: 0 })"#;
    let result_as_string = format!("{:?}", value);
    assert_eq!(expected_string, result_as_string);

    Ok(())
}

#[test]
fn yaml_loader3() -> Result<()> {
    let docs = r###"
Resources:
  MyEc2Thing:
    Type: AWS::EC2::CapacityReservation
    Properties:
      AvailabilityZone: "some string"
      EbsOptimized: true
      EndDate: "12/31/2023"
      EphemeralStorage: false
      InstanceCount: 3.12
      InstanceMatchCriteria: "open"
      TagSpecifications:
      - ResourceType: instance
        Tags:
        - Key: Name
          Value: CFN EC2 Spot Instance
      Tenancy: String
    "###;

    let mut loader = Loader::new();
    let value = loader.load(String::from(docs))?;

    let expected_string = r#"Map({("Resources", Location { line: 1, col: 0 }): Map({("MyEc2Thing", Location { line: 2, col: 2 }): Map({("Type", Location { line: 3, col: 4 }): String("AWS::EC2::CapacityReservation", Location { line: 3, col: 10 }), ("Properties", Location { line: 4, col: 4 }): Map({("AvailabilityZone", Location { line: 5, col: 6 }): String("some string", Location { line: 5, col: 24 }), ("EbsOptimized", Location { line: 6, col: 6 }): Bool(true, Location { line: 6, col: 20 }), ("EndDate", Location { line: 7, col: 6 }): String("12/31/2023", Location { line: 7, col: 15 }), ("EphemeralStorage", Location { line: 8, col: 6 }): Bool(false, Location { line: 8, col: 24 }), ("InstanceCount", Location { line: 9, col: 6 }): Float(3.12, Location { line: 9, col: 21 }), ("InstanceMatchCriteria", Location { line: 10, col: 6 }): String("open", Location { line: 10, col: 29 }), ("TagSpecifications", Location { line: 11, col: 6 }): List([Map({("ResourceType", Location { line: 12, col: 8 }): String("instance", Location { line: 12, col: 22 }), ("Tags", Location { line: 13, col: 8 }): List([Map({("Key", Location { line: 14, col: 10 }): String("Name", Location { line: 14, col: 15 }), ("Value", Location { line: 15, col: 10 }): String("CFN EC2 Spot Instance", Location { line: 15, col: 17 })}, Location { line: 14, col: 10 })], Location { line: 14, col: 8 })}, Location { line: 12, col: 8 })], Location { line: 12, col: 6 }), ("Tenancy", Location { line: 16, col: 6 }): String("String", Location { line: 16, col: 15 })}, Location { line: 5, col: 6 })}, Location { line: 3, col: 4 })}, Location { line: 2, col: 2 })}, Location { line: 1, col: 0 })"#;
    let result_as_string = format!("{:?}", value);
    assert_eq!(expected_string, result_as_string);

    Ok(())
}

#[test]
fn yaml_loader4() -> Result<()> {
    let docs = r###"
Resources:
  MyEc2Thing:
    Type: AWS::EC2::CapacityReservation
    Properties:
      AvailabilityZone: "some string"
      EbsOptimized: true
      EndDate: "12/31/2023"
      EphemeralStorage: !!bool "false"
      InstanceCount: !!float "3.12"
      InstanceMatchCriteria: "open"
      TagSpecifications:
      - ResourceType: instance
        Tags:
        - Key: Name
          Value: CFN EC2 Spot Instance
      Tenancy: String
    "###;

    let mut loader = Loader::new();
    let value = loader.load(String::from(docs))?;

    let expected_string = r#"Map({("Resources", Location { line: 1, col: 0 }): Map({("MyEc2Thing", Location { line: 2, col: 2 }): Map({("Type", Location { line: 3, col: 4 }): String("AWS::EC2::CapacityReservation", Location { line: 3, col: 10 }), ("Properties", Location { line: 4, col: 4 }): Map({("AvailabilityZone", Location { line: 5, col: 6 }): String("some string", Location { line: 5, col: 24 }), ("EbsOptimized", Location { line: 6, col: 6 }): Bool(true, Location { line: 6, col: 20 }), ("EndDate", Location { line: 7, col: 6 }): String("12/31/2023", Location { line: 7, col: 15 }), ("EphemeralStorage", Location { line: 8, col: 6 }): Bool(false, Location { line: 8, col: 24 }), ("InstanceCount", Location { line: 9, col: 6 }): Float(3.12, Location { line: 9, col: 21 }), ("InstanceMatchCriteria", Location { line: 10, col: 6 }): String("open", Location { line: 10, col: 29 }), ("TagSpecifications", Location { line: 11, col: 6 }): List([Map({("ResourceType", Location { line: 12, col: 8 }): String("instance", Location { line: 12, col: 22 }), ("Tags", Location { line: 13, col: 8 }): List([Map({("Key", Location { line: 14, col: 10 }): String("Name", Location { line: 14, col: 15 }), ("Value", Location { line: 15, col: 10 }): String("CFN EC2 Spot Instance", Location { line: 15, col: 17 })}, Location { line: 14, col: 10 })], Location { line: 14, col: 8 })}, Location { line: 12, col: 8 })], Location { line: 12, col: 6 }), ("Tenancy", Location { line: 16, col: 6 }): String("String", Location { line: 16, col: 15 })}, Location { line: 5, col: 6 })}, Location { line: 3, col: 4 })}, Location { line: 2, col: 2 })}, Location { line: 1, col: 0 })"#;
    let result_as_string = format!("{:?}", value);
    assert_eq!(expected_string, result_as_string);

    Ok(())
}

#[test]
fn yaml_loader5() -> Result<()> {
    let docs = r###"
Resources:
  MyEc2Thing:
    Type: AWS::EC2::CapacityReservation
    Properties:
      AvailabilityZone: "some string"
      EbsOptimized: true
      EndDate: "12/31/2023"
      EphemeralStorage: false
      InstanceCount: !!int "312"
      InstanceMatchCriteria: "open"
      TagSpecifications:
      - ResourceType: instance
        Tags:
        - Key: Name
          Value: CFN EC2 Spot Instance
      Tenancy: !!null "String"
    "###;

    let mut loader = Loader::new();
    let value = loader.load(String::from(docs))?;

    let expected_string = r#"Map({("Resources", Location { line: 1, col: 0 }): Map({("MyEc2Thing", Location { line: 2, col: 2 }): Map({("Type", Location { line: 3, col: 4 }): String("AWS::EC2::CapacityReservation", Location { line: 3, col: 10 }), ("Properties", Location { line: 4, col: 4 }): Map({("AvailabilityZone", Location { line: 5, col: 6 }): String("some string", Location { line: 5, col: 24 }), ("EbsOptimized", Location { line: 6, col: 6 }): Bool(true, Location { line: 6, col: 20 }), ("EndDate", Location { line: 7, col: 6 }): String("12/31/2023", Location { line: 7, col: 15 }), ("EphemeralStorage", Location { line: 8, col: 6 }): Bool(false, Location { line: 8, col: 24 }), ("InstanceCount", Location { line: 9, col: 6 }): Int(312, Location { line: 9, col: 21 }), ("InstanceMatchCriteria", Location { line: 10, col: 6 }): String("open", Location { line: 10, col: 29 }), ("TagSpecifications", Location { line: 11, col: 6 }): List([Map({("ResourceType", Location { line: 12, col: 8 }): String("instance", Location { line: 12, col: 22 }), ("Tags", Location { line: 13, col: 8 }): List([Map({("Key", Location { line: 14, col: 10 }): String("Name", Location { line: 14, col: 15 }), ("Value", Location { line: 15, col: 10 }): String("CFN EC2 Spot Instance", Location { line: 15, col: 17 })}, Location { line: 14, col: 10 })], Location { line: 14, col: 8 })}, Location { line: 12, col: 8 })], Location { line: 12, col: 6 }), ("Tenancy", Location { line: 16, col: 6 }): Null(Location { line: 16, col: 15 })}, Location { line: 5, col: 6 })}, Location { line: 3, col: 4 })}, Location { line: 2, col: 2 })}, Location { line: 1, col: 0 })"#;
    let result_as_string = format!("{:?}", value);
    assert_eq!(expected_string, result_as_string);

    Ok(())
}

#[test]
fn yaml_loader6() -> Result<()> {
    let docs = r###"
Resources:
  MyEc2Thing:
    Type: AWS::EC2::CapacityReservation
    Properties:
      AvailabilityZone: "some string"
      EbsOptimized: true
      EndDate: "12/31/2023"
      EphemeralStorage: false
      InstanceCount: !!int "312"
      InstanceMatchCriteria: "open"
      TagSpecifications:
      - ResourceType: instance
        Tags:
        - Key: Name
          Value: CFN EC2 Spot Instance
      Tenancy: !!null "~"
    "###;

    let mut loader = Loader::new();
    let value = loader.load(String::from(docs))?;

    let expected_string = r#"Map({("Resources", Location { line: 1, col: 0 }): Map({("MyEc2Thing", Location { line: 2, col: 2 }): Map({("Type", Location { line: 3, col: 4 }): String("AWS::EC2::CapacityReservation", Location { line: 3, col: 10 }), ("Properties", Location { line: 4, col: 4 }): Map({("AvailabilityZone", Location { line: 5, col: 6 }): String("some string", Location { line: 5, col: 24 }), ("EbsOptimized", Location { line: 6, col: 6 }): Bool(true, Location { line: 6, col: 20 }), ("EndDate", Location { line: 7, col: 6 }): String("12/31/2023", Location { line: 7, col: 15 }), ("EphemeralStorage", Location { line: 8, col: 6 }): Bool(false, Location { line: 8, col: 24 }), ("InstanceCount", Location { line: 9, col: 6 }): Int(312, Location { line: 9, col: 21 }), ("InstanceMatchCriteria", Location { line: 10, col: 6 }): String("open", Location { line: 10, col: 29 }), ("TagSpecifications", Location { line: 11, col: 6 }): List([Map({("ResourceType", Location { line: 12, col: 8 }): String("instance", Location { line: 12, col: 22 }), ("Tags", Location { line: 13, col: 8 }): List([Map({("Key", Location { line: 14, col: 10 }): String("Name", Location { line: 14, col: 15 }), ("Value", Location { line: 15, col: 10 }): String("CFN EC2 Spot Instance", Location { line: 15, col: 17 })}, Location { line: 14, col: 10 })], Location { line: 14, col: 8 })}, Location { line: 12, col: 8 })], Location { line: 12, col: 6 }), ("Tenancy", Location { line: 16, col: 6 }): Null(Location { line: 16, col: 15 })}, Location { line: 5, col: 6 })}, Location { line: 3, col: 4 })}, Location { line: 2, col: 2 })}, Location { line: 1, col: 0 })"#;
    let result_as_string = format!("{:?}", value);
    assert_eq!(expected_string, result_as_string);

    Ok(())
}

#[test]
fn yaml_loader7() -> Result<()> {
    let docs = r###"
Resources:
  MyEc2Thing:
    Type: AWS::EC2::CapacityReservation
    Properties:
      AvailabilityZone: "some string"
      EbsOptimized: true
      EndDate: "12/31/2023"
      EphemeralStorage: false
      InstanceCount: !!int "3x12"
      InstanceMatchCriteria: "open"
      TagSpecifications:
      - ResourceType: instance
        Tags:
        - Key: Name
          Value: CFN EC2 Spot Instance
      Tenancy: !!null "~"
    "###;

    let mut loader = Loader::new();
    let value = loader.load(String::from(docs))?;

    let expected_string = r#"Map({("Resources", Location { line: 1, col: 0 }): Map({("MyEc2Thing", Location { line: 2, col: 2 }): Map({("Type", Location { line: 3, col: 4 }): String("AWS::EC2::CapacityReservation", Location { line: 3, col: 10 }), ("Properties", Location { line: 4, col: 4 }): Map({("AvailabilityZone", Location { line: 5, col: 6 }): String("some string", Location { line: 5, col: 24 }), ("EbsOptimized", Location { line: 6, col: 6 }): Bool(true, Location { line: 6, col: 20 }), ("EndDate", Location { line: 7, col: 6 }): String("12/31/2023", Location { line: 7, col: 15 }), ("EphemeralStorage", Location { line: 8, col: 6 }): Bool(false, Location { line: 8, col: 24 }), ("InstanceCount", Location { line: 9, col: 6 }): BadValue("3x12", Location { line: 9, col: 21 }), ("InstanceMatchCriteria", Location { line: 10, col: 6 }): String("open", Location { line: 10, col: 29 }), ("TagSpecifications", Location { line: 11, col: 6 }): List([Map({("ResourceType", Location { line: 12, col: 8 }): String("instance", Location { line: 12, col: 22 }), ("Tags", Location { line: 13, col: 8 }): List([Map({("Key", Location { line: 14, col: 10 }): String("Name", Location { line: 14, col: 15 }), ("Value", Location { line: 15, col: 10 }): String("CFN EC2 Spot Instance", Location { line: 15, col: 17 })}, Location { line: 14, col: 10 })], Location { line: 14, col: 8 })}, Location { line: 12, col: 8 })], Location { line: 12, col: 6 }), ("Tenancy", Location { line: 16, col: 6 }): Null(Location { line: 16, col: 15 })}, Location { line: 5, col: 6 })}, Location { line: 3, col: 4 })}, Location { line: 2, col: 2 })}, Location { line: 1, col: 0 })"#;
    let result_as_string = format!("{:?}", value);
    assert_eq!(expected_string, result_as_string);

    Ok(())
}

#[test]
fn yaml_loader8() -> Result<()> {
    let docs = r###"
Resources:
  MyEc2Thing:
    Type: AWS::EC2::CapacityReservation
    Properties:
      AvailabilityZone: "some string"
      EbsOptimized: true
      EndDate: "12/31/2023"
      EphemeralStorage: false
      InstanceCount: !!int "3x12"
      InstanceMatchCriteria: "open"
      TagSpecifications:
      - ResourceType: instance
        Tags:
        - Key: Name
          Value: CFN EC2 Spot Instance
      Tenancy: !!str ~
    "###;

    let mut loader = Loader::new();
    let value = loader.load(String::from(docs))?;

    let expected_string = r#"Map({("Resources", Location { line: 1, col: 0 }): Map({("MyEc2Thing", Location { line: 2, col: 2 }): Map({("Type", Location { line: 3, col: 4 }): String("AWS::EC2::CapacityReservation", Location { line: 3, col: 10 }), ("Properties", Location { line: 4, col: 4 }): Map({("AvailabilityZone", Location { line: 5, col: 6 }): String("some string", Location { line: 5, col: 24 }), ("EbsOptimized", Location { line: 6, col: 6 }): Bool(true, Location { line: 6, col: 20 }), ("EndDate", Location { line: 7, col: 6 }): String("12/31/2023", Location { line: 7, col: 15 }), ("EphemeralStorage", Location { line: 8, col: 6 }): Bool(false, Location { line: 8, col: 24 }), ("InstanceCount", Location { line: 9, col: 6 }): BadValue("3x12", Location { line: 9, col: 21 }), ("InstanceMatchCriteria", Location { line: 10, col: 6 }): String("open", Location { line: 10, col: 29 }), ("TagSpecifications", Location { line: 11, col: 6 }): List([Map({("ResourceType", Location { line: 12, col: 8 }): String("instance", Location { line: 12, col: 22 }), ("Tags", Location { line: 13, col: 8 }): List([Map({("Key", Location { line: 14, col: 10 }): String("Name", Location { line: 14, col: 15 }), ("Value", Location { line: 15, col: 10 }): String("CFN EC2 Spot Instance", Location { line: 15, col: 17 })}, Location { line: 14, col: 10 })], Location { line: 14, col: 8 })}, Location { line: 12, col: 8 })], Location { line: 12, col: 6 }), ("Tenancy", Location { line: 16, col: 6 }): String("~", Location { line: 16, col: 15 })}, Location { line: 5, col: 6 })}, Location { line: 3, col: 4 })}, Location { line: 2, col: 2 })}, Location { line: 1, col: 0 })"#;
    let result_as_string = format!("{:?}", value);
    assert_eq!(expected_string, result_as_string);

    Ok(())
}

#[test]
fn yaml_loader_with_alias() -> Result<()> {
    let docs = r###"
a: &numbers
- 1
- 2
- 3
b: *numbers
    "###;

    let mut loader = Loader::new();
    let value = loader.load(String::from(docs));
    assert!(value.is_err());

    Ok(())
}

#[test]
fn test_handle_null() {
    let docs = r###"
    Resources: NULL
    "###;

    let mut loader = Loader::new();
    let value = loader.load(String::from(docs)).unwrap();

    let map = match &value {
        MarkedValue::Map(m, _) => m,
        _ => unreachable!(),
    };

    let val = map
        .get(&("Resources".to_string(), Location::new(1, 4)))
        .unwrap()
        .to_owned();

    assert!(matches!(val, MarkedValue::Null(_)));

    let docs = r###"
    Resources: ~
    "###;

    let value = loader.load(String::from(docs)).unwrap();

    let map = match &value {
        MarkedValue::Map(m, _) => m,
        _ => unreachable!(),
    };

    let val = map
        .get(&("Resources".to_string(), Location::new(1, 4)))
        .unwrap()
        .to_owned();

    assert!(matches!(val, MarkedValue::Null(_)));

    let docs = r###"
    Resources: "~"
    "###;

    let value = loader.load(String::from(docs)).unwrap();

    let map = match &value {
        MarkedValue::Map(m, _) => m,
        _ => unreachable!(),
    };

    let val = map
        .get(&("Resources".to_string(), Location::new(1, 4)))
        .unwrap()
        .to_owned();

    assert!(matches!(val, MarkedValue::String(..)));

    let docs = r###"
    Resources: "null"
    "###;

    let value = loader.load(String::from(docs)).unwrap();

    let map = match &value {
        MarkedValue::Map(m, _) => m,
        _ => unreachable!(),
    };

    let val = map
        .get(&("Resources".to_string(), Location::new(1, 4)))
        .unwrap()
        .to_owned();

    assert!(matches!(val, MarkedValue::String(..)));
}
